//#include <linux/config.h>
#include <linux/version.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/ioport.h>
#include <linux/delay.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/cacheflush.h>

//#define CONFIG_TS_DEBUG

#ifdef CONFIG_TS_DEBUG
#define dprintk(msg...)	printk("YunYun: " msg)
#else
#define dprintk(msg...)
#endif

#define TS_MAJOR      120

#define TS_REG_BASE   na_touchcontroller_0
#define TS_IRQ        na_touchcontroller_0_irq

#define MAX_EVENT_COUNT 32

#define TSIOCTL_DEVICE_CONTROL 0x5453
#define TSIOCTL_MASK_INTR      0x5454
#define TSIOCTL_GET_COORDINATE 0x5455
#define TSIOCTL_WAIT_EVENT     0x5456

/* User Application Commands */
#define TS_INT_BOTH_DISABLE  0x0
#define TS_INT_PRESS_ONLY    0x1
#define TS_INT_RELEASE_ONLY  0x2
#define TS_INT_BOTH_ENABLE   0x3

/* Event Types Flags */
#define FLAG_NONE     0x1
#define FLAG_PRESS    0x2
#define FLAG_RELEASE  0x4
#define FLAG_CLICK    0x8
#define FLAG_DBCLICK  0x10
#define FLAG_DRAG     0x20
int flag = FLAG_NONE;

#define REG0(base) ((u32)(base))

typedef struct {
	u16 x;
	u16 y;
} COORD;

typedef struct ts_reg_t {
	u32 x: 12,
		  y: 12,
	  	pirq: 1,
	  	rirq: 1,
	  	pmask: 1,
	  	rmask: 1,
	  	pressed: 1,
	  	enable: 1,
	  	reserved: 2;
} TS_REG;

typedef struct {
	int flag;
	COORD pos;
	unsigned long timestamp;
	struct list_head list;
} EVENT;

/* Global variables */
TS_REG *ts_reg_addr;
static DECLARE_WAIT_QUEUE_HEAD(wq);
static LIST_HEAD(event_list);
static unsigned int event_list_count = 0;
static spinlock_t event_lock;
static unsigned long event_lock_flags;
static EVENT *handled_event;
static int open_count = 0;

/* Touch Screen ISR */
irqreturn_t ts_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	u32 ts_reg;
	EVENT *event;
	
	if(event_list_count >= MAX_EVENT_COUNT)
		return IRQ_HANDLED;
		
	event = kmalloc(sizeof(EVENT), GFP_ATOMIC);
	if(!event) {
		printk("Warning: failed to allocate kernel memory for event\n");
		return IRQ_HANDLED;
	}
		
	/* Insert new event to the event list */
	list_add_tail(&event->list, &event_list);
	++event_list_count;
	
	ts_reg = readl(REG0(ts_reg_addr));
	event->pos.x = ((TS_REG *)&ts_reg)->x;
	event->pos.y = ((TS_REG *)&ts_reg)->y;
	//dprintk("X: %d, Y: %d\n", event->pos.x, event->pos.y);
	
	if(((TS_REG *)&ts_reg)->pirq) {	/* Press interrupt */
	  event->flag = FLAG_PRESS;
	  ((TS_REG *)&ts_reg)->pirq = 0; /* Clear pirq */
	  writel(ts_reg, REG0(ts_reg_addr));
	  dprintk("Press Interrupted!");
	}
	else if(((TS_REG *)&ts_reg)->rirq) {      /* Release interrupt */
	  event->flag = FLAG_RELEASE;
	  ((TS_REG *)&ts_reg)->rirq = 0; /* Clear rirq */
	  writel(ts_reg, REG0(ts_reg_addr));
	  dprintk("Release Interrupted!");
	}
	else {
		dprintk("Interrupt register: %x\n", ts_reg);
		return IRQ_NONE;
	}
	
	event->timestamp = jiffies;
	wake_up_interruptible(&wq);

	return IRQ_HANDLED;
}

int ts_open(struct inode *inode, struct file *filp)
{
	int result;
	
	if(open_count > 0)
		return 0;
	
	++open_count;
	if((result = request_irq(TS_IRQ, ts_interrupt, SA_INTERRUPT, "YunYun Touch Screen", NULL)) < 0) {
		  printk(KERN_INFO "YunYun: Can't get assigned irq\n");
		  return result;
	}
	dprintk("Interrupt installed on IRQ %d\n", TS_IRQ);
	
	return 0;
}

int ts_release(struct inode *inode, struct file *filp)
{
	EVENT *pos, *n;
	
	if(open_count <= 0) {
		open_count = 0;
		return 0;
	}
	
	--open_count;
	if(open_count == 0) {
		free_irq(TS_IRQ, NULL);
		spin_lock_irqsave(&event_lock, event_lock_flags);
		list_for_each_entry_safe(pos, n, &event_list, list) {
			list_del(&pos->list);
			kfree(pos);
		}
		event_list_count = 0;
		spin_unlock_irqrestore(&event_lock, event_lock_flags);
	}
	return 0;
}

int ts_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
	u8 val;
	u32 ts_reg;
	EVENT *pos, *n;
	
	switch (cmd) {
		case TSIOCTL_DEVICE_CONTROL:
			if(copy_from_user(&val, (void __user *)arg, sizeof(u8)))
				return -EINVAL;
			ts_reg = readl(REG0(ts_reg_addr));
			if(val) {
				((TS_REG *)&ts_reg)->enable = 1;
			}
			else {
				((TS_REG *)&ts_reg)->enable = 0;
				spin_lock_irqsave(&event_lock, event_lock_flags);
				list_for_each_entry_safe(pos, n, &event_list, list) {
					list_del(&pos->list);
					kfree(pos);
				}
				event_list_count = 0;
				spin_unlock_irqrestore(&event_lock, event_lock_flags);
			}
			writel(ts_reg, REG0(ts_reg_addr));
			break;
			
		case TSIOCTL_MASK_INTR:
			if(copy_from_user(&val, (void __user *)arg, sizeof(u8)))
				return -EINVAL;
			ts_reg = readl(REG0(ts_reg_addr));
			switch(val) {
				case TS_INT_BOTH_DISABLE:
					dprintk("Both press and release interrupt disabled\n");
					((TS_REG *)&ts_reg)->pmask = 0;
					((TS_REG *)&ts_reg)->rmask = 0;
					break;
				case TS_INT_PRESS_ONLY: 
					dprintk("Press interrupt enabled\n");
					((TS_REG *)&ts_reg)->pmask = 1;
					break;
				case TS_INT_RELEASE_ONLY: 
					dprintk("Release interrupt enabled\n");
					((TS_REG *)&ts_reg)->rmask = 1;
					break;
				case TS_INT_BOTH_ENABLE: 
					dprintk("Both press and release interrupt enabled\n");
					((TS_REG *)&ts_reg)->pmask = 1;
					((TS_REG *)&ts_reg)->rmask = 1;
					break;
				default:
					dprintk("Both press and release interrupt enabled\n");
					((TS_REG *)&ts_reg)->pmask = 1;
					((TS_REG *)&ts_reg)->rmask = 1;
					break;
			}
			writel(ts_reg, REG0(ts_reg_addr));
			break;
			
		case TSIOCTL_GET_COORDINATE:
			if(handled_event)
				return copy_to_user((void __user *)arg, &handled_event->pos, sizeof(COORD)) ? -EFAULT : 0;
			return -EFAULT;
		
		case TSIOCTL_WAIT_EVENT:
			/* Wait until event list is not empty */
			if(wait_event_interruptible(wq, !list_empty(&event_list)))
				return -ERESTARTSYS;
					
			if(handled_event)
				kfree(handled_event);
			
			if(list_empty(&event_list)) {
				dprintk("Warning, event list is empty, this condition should not happen!\n");
				return -EIO;
			}
				
			/* Fetch and remove a event from the event list */
			spin_lock_irqsave(&event_lock, event_lock_flags);
			handled_event = list_entry(event_list.next, EVENT, list);
			list_del(&handled_event->list);
			--event_list_count;
			spin_unlock_irqrestore(&event_lock, event_lock_flags);
			
			return copy_to_user((void __user *)arg, handled_event, 
			                    sizeof(EVENT) - sizeof(struct list_head)) ? -EFAULT : 0;
	}
		
	return 0;
}

struct file_operations ts_fops = {
	ioctl:	ts_ioctl,
	open:		ts_open,
	release:	ts_release,
};

static int __init ts_init(void)
{
	int result;
	u32 init_val;
	
	if (!request_mem_region((unsigned long)TS_REG_BASE, sizeof(TS_REG), "Touch Screen Register")) {
		return -EBUSY;
	}
	dprintk("Got Touch Screen Register\n");
	
	ts_reg_addr = (TS_REG __iomem *)ioremap((unsigned long)TS_REG_BASE, sizeof(TS_REG));
	
	result = register_chrdev(TS_MAJOR, "YunYun Touch Screen", &ts_fops);
	if(result < 0) {
		printk(KERN_WARNING "YunYun: Can't get major %d\n", TS_MAJOR);
		release_mem_region((unsigned long)TS_REG_BASE, sizeof(TS_REG));
		return result;
	}
	
	/* Initialize event list lock */
	spin_lock_init(&event_lock);
	
	/* Initialize touch controller */
	((TS_REG *)&init_val)->pmask = 1;
	((TS_REG *)&init_val)->rmask = 1;
	((TS_REG *)&init_val)->enable = 1;
	writel(init_val, REG0(ts_reg_addr));
	return 0;
}

static void __exit ts_cleanup(void) 
{
	unregister_chrdev(TS_MAJOR, "YunYun Touch Screen");
	release_mem_region((unsigned long)TS_REG_BASE, sizeof(TS_REG));
	printk("YunYun Touch Screen driver removed!\n");
}

module_init(ts_init);
module_exit(ts_cleanup);

MODULE_AUTHOR("Kenny, YunYun");
MODULE_DESCRIPTION("Touch Screen driver for the NIOS2 Dashboard System");
MODULE_LICENSE("GPL");
